package qc.module.qms.api.controller;

import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

/**
 * WebSocketServer
 *
 * @author QuCheng Tech
 * @since 2025/2/18
 */
@Component
//对外暴露访问ws应用的url；格式固定为：/websocket/{topic}/{requestId}；topic用于指示消息主题，后继定时推送给客户端消息根据topic进行推送；requestId用于唯一标识请求客户端，由客户端生成传入
@ServerEndpoint("/websocket/{topic}/{requestId}")
public class WebSocketServer {
    /**
     * 静态变量，用来记录当前在线连接数。应该把它设计成线程安全的。
     */
    private static int onlineCount = 0;
    /**
     * concurrent包的线程安全Set，用来存放每个客户端对应的MyWebSocket对象。
     * key为主题代码，value为主题中的客户端和WebSocketServer对应信息集合
     */
    private static ConcurrentHashMap<String, Map<String, WebSocketServer>> webSocketMap = new ConcurrentHashMap<>();
    /**
     * 每个主题对应的最新消息，用于推送消息使用
     */
    private static ConcurrentHashMap<String, String> topicAndMessageMap = new ConcurrentHashMap<>();
    /**
     * 与某个客户端的连接会话，需要通过它来给客户端发送数据
     */
    private Session session;
    /**
     * 请求指定的主题
     */
    private String topic = "";
    /**
     * 请求客户端ID
     */
    private String requestId = "";

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("topic") String topic, @PathParam("requestId") String requestId) {
        //public void onOpen(Session session, @RequestParam("requestId") String requestId) {
        //前端使用QueryParams传值时使用PathParam无法获取值，修改为使用url路由在ServerEndpoint指定url中获取
        this.session = session;
        this.topic = topic;
        //如果请求客户端ID为空，自动生成一个
        if (StringUtils.isNotBlank(requestId))
            this.requestId = requestId;
        else
            this.requestId = UUID.randomUUID().toString();

        System.out.println("客户端连接:topic=" + this.topic + " requestId=" + this.requestId + " session=" + this.session);

        //从URL参数中获取客户端标识，通过url后？方式传参
        String requestUriQueryString = session.getRequestURI().getQuery();
        System.out.println("客户端连接:requestUriQueryString=" + requestUriQueryString);

        //要求主题不为空
        if (StringUtils.isNotBlank(this.topic)) {
            if (webSocketMap.containsKey(this.topic)) {
                //map中已有该主题
                Map<String, WebSocketServer> topicWebSocketMap = webSocketMap.get(this.topic);

                //判断是否存在相同的客户端，如果存在进行更新
                if (topicWebSocketMap != null && topicWebSocketMap.size() > 0x0 && topicWebSocketMap.containsKey(this.requestId)) {
                    //移替换已存在的客户端信息
                    topicWebSocketMap.replace(this.requestId, this);
                } else {
                    //客户端不存在，添加
                    topicWebSocketMap.put(this.requestId, this);

                    //在线数加1
                    addOnlineCount();
                }

                //先移除，再添加;直接替换已有
                webSocketMap.replace(this.topic, topicWebSocketMap);
                //加入set中
            } else {
                //map中没有该主题
                Map<String, WebSocketServer> topicWebSocketMap = new HashMap<>();

                topicWebSocketMap.put(requestId, this);
                //加入该主题的map
                webSocketMap.put(this.topic, topicWebSocketMap);

                //在线数加1
                addOnlineCount();
            }

            //客户端连接后，进行一次数据推送
            try {
                sendMessage();
            } catch (IOException e) {
                System.out.println("用户:" + requestId + ",网络异常!!!!!!");
            }
        }

        System.out.println("客户端连接:" + requestId + ",当前在线客户端为:" + getOnlineCount());
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        System.out.println("客户端关闭:topic=" + this.topic + " requestId=" + this.requestId + " session=" + this.session);

        if (StringUtils.isNotBlank(this.topic) && StringUtils.isNotBlank(this.requestId)) {
            if (webSocketMap.containsKey(this.topic)) {
                Map<String, WebSocketServer> topicWebSocketMap = webSocketMap.get(this.topic);
                if (topicWebSocketMap != null && topicWebSocketMap.size() > 0x0 && topicWebSocketMap.containsKey(this.requestId)) {
                    //移除已存在的客户端信息
                    topicWebSocketMap.remove(this.requestId);
                    //更新map
                    webSocketMap.replace(this.topic, topicWebSocketMap);

                    //在线数量减1
                    subOnlineCount();
                }
            }
        }
        System.out.println("客户端关闭:" + requestId + ",当前在线客户端为:" + getOnlineCount());
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        System.out.println("客户端消息:topic=" + this.topic + " requestId=" + this.requestId + " session=" + this.session);

        //可以群发消息
        //消息保存到数据库、redis
        //if (!StringUtils.isEmpty(message)) {
        //    try {
        //        //解析发送的报文
        //        JSONObject jsonObject = JSON.parseObject(message);
        //
        //    } catch (Exception e) {
        //        e.printStackTrace();
        //    }
        //}
    }

    /**
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        System.out.println("客户端错误:topic=" + this.topic + " requestId=" + this.requestId + " session=" + this.session);

        error.printStackTrace();
    }

    /**
     * 发送消息，在客户端连接后调用1次进行消息推送
     */
    public void sendMessage() throws IOException {
        if (StringUtils.isNotBlank(this.topic) && StringUtils.isNotBlank(this.requestId)) {
            //更新缓存的消息集合
            if (topicAndMessageMap != null && topicAndMessageMap.size() > 0x0 && topicAndMessageMap.containsKey(this.topic)) {
                String message = topicAndMessageMap.get(this.topic);
                if (StringUtils.isNotBlank(message)) {
                    //存在该主题的消息，进行推送
                    this.session.getBasicRemote().sendText(message);
                }
            }
        }
    }

    /***
     * 向指定主题的客户端发送消息，消息内容使用最新缓存的信息，如果没有最新消息不进行推送
     *
     * @param topic 主题代码
     * @param requestId 客户端ID
     * @return void
     * @author QuCheng Tech
     * @since 2025/2/18
     */
    public static void sendMessage(String topic, String requestId) throws IOException {
        if (StringUtils.isNotBlank(topic) && StringUtils.isNotBlank(requestId)) {
            //更新缓存的消息集合
            if (topicAndMessageMap != null && topicAndMessageMap.size() > 0x0 && topicAndMessageMap.containsKey(topic)) {
                String message = topicAndMessageMap.get(topic);
                if (StringUtils.isNotBlank(message)) {
                    //存在该主题的消息，进行推送
                    //获取该主题的客户端集合，逐个进行消息推送
                    if (webSocketMap != null && webSocketMap.size() > 0x0 && webSocketMap.containsKey(topic)) {
                        Map<String, WebSocketServer> topicWebSocketMap = webSocketMap.get(topic);
                        if (topicWebSocketMap != null && topicWebSocketMap.size() > 0x0 && topicWebSocketMap.containsKey(requestId)) {
                            WebSocketServer webSocketServer = topicWebSocketMap.get(requestId);

                            webSocketServer.session.getBasicRemote().sendText(message);

                            System.out.println("webSocket服务器主动推送:topic=" + topic + " requestId=" + requestId + " message=" + message);
                        }
                    }
                }
            }
        }
    }

    /***
     * 向指定主题发送消息
     *
     * @param topic 主题代码
     * @param message 消息内容
     * @return void
     * @author QuCheng Tech
     * @since 2025/2/18
     */
    public static void sendTopicMessage(String topic, String message) throws IOException {
        System.out.println("webSocket服务器主动推送:topic=" + topic + " message=" + message);

        if (StringUtils.isNotBlank(topic)) {
            //更新缓存的消息集合
            if (topicAndMessageMap != null && topicAndMessageMap.size() > 0x0 && topicAndMessageMap.containsKey(topic)) {
                //存在相同的主题，更新消息
                topicAndMessageMap.replace(topic, message);
            } else {
                //不存在该主题的消息，添加
                topicAndMessageMap.put(topic, message);
            }

            //获取该主题的客户端集合，逐个进行消息推送
            if (webSocketMap != null && webSocketMap.size() > 0x0 && webSocketMap.containsKey(topic)) {
                Map<String, WebSocketServer> topicWebSocketMap = webSocketMap.get(topic);
                if (topicWebSocketMap != null && topicWebSocketMap.size() > 0x0) {
                    for (Map.Entry<String, WebSocketServer> entry : topicWebSocketMap.entrySet()) {
                        String requestId = entry.getKey();
                        WebSocketServer webSocketServer = entry.getValue();

                        webSocketServer.session.getBasicRemote().sendText(message);

                        System.out.println("webSocket服务器主动推送:topic=" + topic + " requestId=" + requestId);
                    }
                }
            }
        }
    }

    public static synchronized int getOnlineCount() {
        return WebSocketServer.onlineCount;
    }

    public static synchronized void addOnlineCount() {
        WebSocketServer.onlineCount++;
    }

    public static synchronized void subOnlineCount() {
        WebSocketServer.onlineCount--;
    }
}
